package com.zrrd.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.zrrd.gmall.list.service.GoodsRespostiory;
import com.zrrd.gmall.list.service.SearchService;
import com.zrrd.gmall.model.list.*;
import com.zrrd.gmall.model.product.*;
import com.zrrd.gmall.product.fegin.ProductFeginClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.swing.*;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    //向商品服务发起请求的fegin对象
    @Autowired
    private ProductFeginClient productFeginClient;
    //操作es的对象
    @Autowired
    private GoodsRespostiory goodsRespostiory;
    //操作redis的对象
    @Autowired
    private RedisTemplate redisTemplate;
    //es中搜索请求的对象
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    //上架商品
    @Override
    public void upperGoods(Long skuId) {
        //创建es索引库中对应的商品实体类对象
        Goods goods = new Goods();

        //设置sku的基本信息
        SkuInfo skuInfo = productFeginClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        goods.setTitle(skuInfo.getSkuName());
        goods.setCreateTime(new Date());

        //设置sku的分类信息
        BaseCategoryView categoryView = productFeginClient.getBaseCategoryView(skuInfo.getCategory3Id());
        if (categoryView != null) {
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());
        }

        //设置sku的品牌信息
        BaseTrademark trademark = productFeginClient.getTrademark(skuInfo.getTmId());
        if (trademark != null) {
            goods.setTmId(trademark.getId());
            goods.setTmLogoUrl(trademark.getLogoUrl());
            goods.setTmName(trademark.getTmName());
        }

        //设置sku的平台属性信息
        List<BaseAttrInfo> baseAttrInfoList = productFeginClient.getAttrList(skuId);
        if (baseAttrInfoList != null && baseAttrInfoList.size() > 0) {
            //使用stream流对集合进行拆分 baseAttrInfo每次遍历集合中的元素
            List<SearchAttr> attrList = baseAttrInfoList.stream().map(baseAttrInfo -> {
                //创建es中对应结构的平台属性对象
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                //获取属性指的集合
                List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();
                searchAttr.setAttrValue(attrValueList.get(0).getValueName());
                //返回对象
                return searchAttr;

            }).collect(Collectors.toList());
            //设置给goods对象
            goods.setAttrs(attrList);
        }
        //使用操作es的对象 执行添加
        goodsRespostiory.save(goods);
    }

    //下架商品
    @Override
    public void lowerGoods(Long skuId) {
        //使用操作es的对象 执行删除
        goodsRespostiory.deleteById(skuId);
    }

    //更新热度
    @Override
    public void incrHotScore(Long skuId) {
        //定义一个redis中热度的key
        String hotKey = "hotScore";
        //将热度以自增1的方式 存入到Redis
        Double score = redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        System.out.println("当前商品===>" + skuId);
        System.out.println("当前热度值===>" + score);

        //只有热度是10的倍数的时候 才更新 很大程度避免ES因为访问量过大崩溃
        if (score % 10 == 0) {
            //根据ID获取es中对应的文档对象
            Optional<Goods> optional = goodsRespostiory.findById(skuId);
            Goods goods = optional.get();
            //给文档对象重新设置热度字段
            goods.setHotScore(Math.round(score));
            //使用操作es的对象 执行更新（添加）
            goodsRespostiory.save(goods);
        }
    }

    //搜索商品
    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        //1.根据用户的搜索条件生成对应的dsl语句
        SearchRequest request = this.buildQueryDsl(searchParam);

        //2.发起请求获取结果
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        //4.调用解析结果的方法
        SearchResponseVo vo = this.parseResponse(response);

        //5.给返回结果的对象设置分页相关字段的值
        vo.setPageNo(searchParam.getPageNo());
        vo.setPageSize(searchParam.getPageSize());
        long totalPages = (vo.getTotal() + searchParam.getPageSize()-1)/searchParam.getPageSize();
        vo.setTotalPages(totalPages);

        return vo;
    }

    //制作DSL语句的方法
    private SearchRequest buildQueryDsl(SearchParam searchParam) {
        System.out.println("收到的搜索条件数据====>" + searchParam);

        //创建一个整体的搜索查询器对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //创建布尔查询器对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //根据分类id进行查询
        if (searchParam.getCategory1Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }
        if (searchParam.getCategory2Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }
        if (searchParam.getCategory3Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }
        //根据关键字搜索
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));
        }
        //根据品牌过滤
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            //切割字符串 16:油桃
            String[] split = trademark.split(":");
            if (split != null && split.length == 2) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", split[0]));
            }
        }
        //根据平台属性值过滤
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0) {
            //遍历数组
            for (String prop : props) {
                //切割字符串   23:4G:运行内存   属性ID:属性值名称:属性名称
                String[] split = prop.split(":");
                if (split != null && split.length == 3) {
                    //创建2个bool查询对象
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    //给子查询器对象 设置平台属性id 和 属性值名称
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                    //将子查询器对象设置给父查询器对象
                    boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));
                    //将boolQuery设置给最外层的查询器对象
                    boolQueryBuilder.filter(boolQuery);
                }

            }
        }
        //将布尔的查询器设置给整个搜索的查询器对象
        searchSourceBuilder.query(boolQueryBuilder);

        //分页处理
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        int from = (pageNo - 1) * pageSize;
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(pageSize);

        //排序处理
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            //切割字符串     1或者2:asc或者desc   1=hotScore 2=price   asc=升序  desc=降序排序
            String[] split = order.split(":");
            //声明一个字符串 代表排序的是哪个字段
            String field = "";
            if (split != null && split.length == 2) {
                switch (split[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                //排序
                SortOrder sortOrder = "asc".equals(split[1]) ? SortOrder.ASC : SortOrder.DESC;
                searchSourceBuilder.sort(field, sortOrder);
            }
        } else {
            //如果没有传递排序规则 默认按照热度 降序排序
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }

        //高亮处理
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        //品牌聚合
        TermsAggregationBuilder tmAgg = AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"));
        searchSourceBuilder.aggregation(tmAgg);

        //平台属性值聚合
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attrAgg", "attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                );
        searchSourceBuilder.aggregation(attrAgg);

        //设置返回结果中只返回某些字段
       // searchSourceBuilder.fetchSource(new String[]{"id","title","price","defaultImg"},null);

        //创建请求对象
        SearchRequest request = new SearchRequest("goods");
        //给请求对象设置DSL语句
        request.source(searchSourceBuilder);
        //打印生成的DSL语句
        System.out.println("生成的DSL====>" + searchSourceBuilder.toString());
        //返回请求对象
        return request;
    }

    //制作返回结果的方法
    private SearchResponseVo parseResponse(SearchResponse searchResponse) {
        //创建最终返回的结果对象
        SearchResponseVo vo = new SearchResponseVo();

        //通过es返回的响应结果对象 获取聚合结果的整体对象 转为Map集合（装载了品牌和平台属性聚合的结果）
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        //获取品牌聚合数据对象
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //将品牌聚合的集合 换算成品牌需要的vo集合
        List<SearchResponseTmVo> tmVoList = tmIdAgg.getBuckets().stream().map((bucket -> {
            //创建品牌聚合的Vo对象
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //设置品牌的id
            String key = ((Terms.Bucket) bucket).getKeyAsString();
            tmVo.setTmId(Long.parseLong(key));
            //设置品牌名称
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            tmVo.setTmName(tmName);
            //设置品牌的Logo
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            tmVo.setTmLogoUrl(tmLogoUrl);
            //返回当前对象
            return tmVo;
        })).collect(Collectors.toList());
        //设置品牌聚合集合数据
        vo.setTrademarkList(tmVoList);

        //获取平台属性的聚合数据对象
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrVoList = attrIdAgg.getBuckets().stream().map((bucket -> {
            //创建平台属性返回结果的Vo对象
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //设置属性ID
            Number attrId = ((Terms.Bucket) bucket).getKeyAsNumber();
            attrVo.setAttrId(attrId.longValue());
            //设置属性名称
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);
            //设置属性值的集合
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            List<String> valueList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            attrVo.setAttrValueList(valueList);
            //返回当前对象
            return attrVo;

        })).collect(Collectors.toList());
        //设置平台属性聚合集合数据
        vo.setAttrsList(attrVoList);


        //获取搜索的商品结果集合
        SearchHit[] hits = searchResponse.getHits().getHits();
        //创建返回的商品的集合
        List<Goods> goodsList = new ArrayList<>();
        //遍历请求回来的结果数组
        if (hits != null && hits.length > 0) {
            for (SearchHit hit : hits) {
                String source = hit.getSourceAsString();
                Goods goods = JSON.parseObject(source, Goods.class);
                //判断是否有高亮数据
                if (hit.getHighlightFields().get("title") != null) {
                    //获取高亮数据
                    String title = hit.getHighlightFields().get("title").getFragments()[0].toString();
                    //给商品对象 设置高亮
                    goods.setTitle(title);
                }
                //添加进集合
                goodsList.add(goods);
            }
        }
        vo.setGoodsList(goodsList);

        //设置商品的总数
        vo.setTotal(searchResponse.getHits().totalHits);
        return vo;
    }
}
